﻿// -------------------------------------------------------------------------------------------------------------------------------
// <copyright file="MatrixExtensions.cs" company="Exul">
//     Copyright (c) Exul. All rights reserved.
// </copyright>
// <author>
//     Exul
// </author>
// -------------------------------------------------------------------------------------------------------------------------------

namespace ExulLibrary.Mathematics.LinearAlgebra.Sequential
{
    #region Usings

    using System;
    using System.Diagnostics.CodeAnalysis;

    using ExulLibrary.Exceptions;
    using ExulLibrary.Mathematics.LinearAlgebra.Exceptions;

    #endregion

    /// <summary>
    ///     Provides extension methods for <see cref="Matrix{T}"/>.
    /// </summary>
    public static class MatrixExtensions
    {
        #region Fields

        #region private

        /// <summary>
        ///     The collection of provider for clear operations.
        /// </summary>
        private static IClearableMatrixOperationsProvider[] ClearOperationsProviders = new IClearableMatrixOperationsProvider[0];

        /// <summary>
        ///     The collection of providers for clone operations.
        /// </summary>
        private static ICloneableMatrixOperationsProvider[] CloneOperationsProviders =
            new ICloneableMatrixOperationsProvider[0];

        /// <summary>
        ///     The collection of providers for common matrix operations.
        /// </summary>
        private static IMatrixOperationsProvider[] CommonOperationsProviders = new IMatrixOperationsProvider[0];

        #endregion

        #endregion

        #region Constructors and Destructors

        #region ...

        /// <summary>
        ///     Initializes static members of the <see cref="MatrixExtensions"/> class.
        /// </summary>
        [SuppressMessage(
            "Microsoft.Maintainability",
            "CA1506:AvoidExcessiveClassCoupling",
            Justification = "This class uses internal providers to hide them from external code.")]
        [SuppressMessage(
            "Microsoft.Performance",
            "CA1810:InitializeReferenceTypeStaticFieldsInline",
            Justification = "Initialization is too complex.")]
        static MatrixExtensions()
        {
            RegisterOperationsProvider(typeof(CcsSparseMatrix<object>), new CcsSparseMatrixCommonOperationsProvider());
            RegisterOperationsProvider(typeof(CMDenseMatrix<object>), new CMDenseMatrixCommonOperationsProvider());
            RegisterOperationsProvider(typeof(CMFlattenDenseMatrix<object>), new CMFlattenDenseMatrixCommonOperationsProvider());
            RegisterOperationsProvider(typeof(CrcsSparseMatrix<object>), new CrcsSparseMatrixCommonOperationsProvider());
            RegisterOperationsProvider(
                typeof(CrcsSparseSymmetricMatrix<object>), new CrcsSparseSymmetricMatrixCommonOperationsProvider());
            RegisterOperationsProvider(typeof(CrsSparseMatrix<object>), new CrsSparseMatrixCommonOperationsProvider());
            RegisterOperationsProvider(typeof(DenseSymmetricMatrix<object>), new DenseSymmetricMatrixCommonOperationsProvider());
            RegisterOperationsProvider(typeof(DiagonalMatrix<object>), new DiagonalMatrixCommonOperationsProvider());
            RegisterOperationsProvider(typeof(RMDenseMatrix<object>), new RMDenseMatrixCommonOperationsProvider());
            RegisterOperationsProvider(typeof(RMFlattenDenseMatrix<object>), new RMFlattenDenseMatrixCommonOperationsProvider());

            RegisterOperationsProvider(typeof(CcsSparseMatrix<object>), new CcsSparseCloneableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(CMDenseMatrix<object>), new CMDenseCloneableMatrixOperationsProvider());
            RegisterOperationsProvider(
                typeof(CMFlattenDenseMatrix<object>), new CMFlattenDenseCloneableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(CrcsSparseMatrix<object>), new CrcsSparseCloneableMatrixOperationsProvider());
            RegisterOperationsProvider(
                typeof(CrcsSparseSymmetricMatrix<object>), new CrcsSparseSymmetricCloneableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(CrsSparseMatrix<object>), new CrsSparseCloneableMatrixOperationsProvider());
            RegisterOperationsProvider(
                typeof(DenseSymmetricMatrix<object>), new DenseSymmetricCloneableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(DiagonalMatrix<object>), new DiagonalCloneableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(RMDenseMatrix<object>), new RMDenseCloneableMatrixOperationsProvider());
            RegisterOperationsProvider(
                typeof(RMFlattenDenseMatrix<object>), new RMFlattenDenseCloneableMatrixOperationsProvider());

            RegisterOperationsProvider(typeof(CcsSparseMatrix<object>), new CcsSparseClearableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(CMDenseMatrix<object>), new CMDenseClearableMatrixOperationsProvider());
            RegisterOperationsProvider(
                typeof(CMFlattenDenseMatrix<object>), new CMFlattenDenseClearableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(CrcsSparseMatrix<object>), new CrcsSparseClearableMatrixOperationsProvider());
            RegisterOperationsProvider(
                typeof(CrcsSparseSymmetricMatrix<object>), new CrcsSparseSymmetricClearableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(CrsSparseMatrix<object>), new CrsSparseClearableMatrixOperationsProvider());
            RegisterOperationsProvider(
                typeof(DenseSymmetricMatrix<object>), new DenseSymmetricClearableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(DiagonalMatrix<object>), new DiagonalClearableMatrixOperationsProvider());
            RegisterOperationsProvider(typeof(RMDenseMatrix<object>), new RMDenseClearableMatrixOperationsProvider());
            RegisterOperationsProvider(
                typeof(RMFlattenDenseMatrix<object>), new RMFlattenDenseClearableMatrixOperationsProvider());
        }

        #endregion

        #endregion

        #region Methods

        #region public

        /// <summary>
        ///     Gets the element in the matrix at the specified location.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of values of matrix elements.
        /// </typeparam>
        /// <param name="matrix">
        ///     The matrix that contains the element to get.
        /// </param>
        /// <param name="rowIndex">
        ///     The zero-based index of row of the element to get.
        /// </param>
        /// <param name="columnIndex">
        ///     The zero-based index of column of the element to get.
        /// </param>
        /// <returns>
        ///     The element in the matrix at the specified location.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrix"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ElementIndexIsOutOfRangeException">
        ///     <paramref name="rowIndex"/> is negative or greater than or equal to
        ///     the number of rows of <paramref name="matrix"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="columnIndex"/> is negative or greater than or equal to
        ///         the number of columns of <paramref name="matrix"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="NotFoundOperationsProviderException">
        ///     A provider to perform this operation with the specified type of matrix is not registered.
        /// </exception>
        public static T GetAt<T>(this Matrix<T> matrix, int rowIndex, int columnIndex)
        {
            #if SAFE

            if (matrix == null)
            {
                throw new ArgumentNullException("matrix");
            }

            if ((CommonOperationsProviders.Length <= matrix.MatrixRegistrationIndex) ||
                (CommonOperationsProviders[matrix.MatrixRegistrationIndex] == null))
            {
                throw new NotFoundOperationsProviderException(typeof(IMatrixOperationsProvider), typeof(MatrixExtensions));
            }

            #endif

            return CommonOperationsProviders[matrix.MatrixRegistrationIndex].GetAt(matrix, rowIndex, columnIndex);
        }

        /// <summary>
        ///     Sets the element in the matrix at the specified location.
        /// </summary>
        /// <typeparam name="T">
        ///     The type of values of matrix elements.
        /// </typeparam>
        /// <param name="matrix">
        ///     The matrix that contains the element to set.
        /// </param>
        /// <param name="newValue">
        ///     The new value for the element.
        /// </param>
        /// <param name="rowIndex">
        ///     The zero-based index of row of the element to set.
        /// </param>
        /// <param name="columnIndex">
        ///     The zero-based index of column of the element to set.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrix"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ElementIndexIsOutOfRangeException">
        ///     <paramref name="rowIndex"/> is negative or greater than or equal to
        ///     the number of rows of <paramref name="matrix"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="columnIndex"/> is negative or greater than or equal to
        ///         the number of columns of <paramref name="matrix"/>.
        ///     </para>
        /// </exception>
        /// <exception cref="ReadOnlyMatrixElementException">
        ///     <paramref name="rowIndex"/> and <paramref name="columnIndex"/> specifies the element
        ///     that is not in the portrait of <paramref name="matrix"/>.
        /// </exception>
        public static void SetAt<T>(this Matrix<T> matrix, T newValue, int rowIndex, int columnIndex)
        {
            #if SAFE

            if (matrix == null)
            {
                throw new ArgumentNullException("matrix");
            }

            if ((CommonOperationsProviders.Length <= matrix.MatrixRegistrationIndex) ||
                (CommonOperationsProviders[matrix.MatrixRegistrationIndex] == null))
            {
                throw new NotFoundOperationsProviderException(typeof(IMatrixOperationsProvider), typeof(MatrixExtensions));
            }

            #endif

            CommonOperationsProviders[matrix.MatrixRegistrationIndex].SetAt(matrix, newValue, rowIndex, columnIndex);
        }

        #endregion

        #region private

        /// <summary>
        ///     Registers the operations provider.
        /// </summary>
        /// <param name="matrixType">
        ///     The type of matrix.
        /// </param>
        /// <param name="provider">
        ///     The provider for common operations.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrixType"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="provider"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        private static void RegisterOperationsProvider(Type matrixType, IMatrixOperationsProvider provider)
        {
            #if SAFE

            if (matrixType == null)
            {
                throw new ArgumentNullException("matrixType");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            #endif

            var index = Global.GetMatrixRegistrationIndex(matrixType);
            if (CommonOperationsProviders.Length <= index)
            {
                Array.Resize(ref CommonOperationsProviders, index + 1);
            }

            CommonOperationsProviders[index] = provider;
        }

        /// <summary>
        ///     Registers the operations provider.
        /// </summary>
        /// <param name="matrixType">
        ///     The type of matrix.
        /// </param>
        /// <param name="provider">
        ///     The provider for clone operations.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrixType"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="provider"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        private static void RegisterOperationsProvider(Type matrixType, ICloneableMatrixOperationsProvider provider)
        {
            #if SAFE

            if (matrixType == null)
            {
                throw new ArgumentNullException("matrixType");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            #endif

            var index = Global.GetMatrixRegistrationIndex(matrixType);
            if (CloneOperationsProviders.Length <= index)
            {
                Array.Resize(ref CloneOperationsProviders, index + 1);
            }

            CloneOperationsProviders[index] = provider;
        }

        /// <summary>
        ///     Registers the operations provider.
        /// </summary>
        /// <param name="matrixType">
        ///     The type of matrix.
        /// </param>
        /// <param name="provider">
        ///     The provider for clear operations.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="matrixType"/> is <see langword="null"/>.
        ///     <para>
        ///         --or--
        ///     </para>
        ///     <para>
        ///         <paramref name="provider"/> is <see langword="null"/>.
        ///     </para>
        /// </exception>
        private static void RegisterOperationsProvider(Type matrixType, IClearableMatrixOperationsProvider provider)
        {
            #if SAFE

            if (matrixType == null)
            {
                throw new ArgumentNullException("matrixType");
            }

            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            #endif

            var index = Global.GetMatrixRegistrationIndex(matrixType);
            if (ClearOperationsProviders.Length <= index)
            {
                Array.Resize(ref ClearOperationsProviders, index + 1);
            }

            ClearOperationsProviders[index] = provider;
        }

        #endregion

        #endregion
    }
}